﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq.Expressions;

namespace SkeletonNET.Crud.KeyCache
{
    /// <summary>
    /// IEntityKeyCache implementations
    /// </summary>
    public class EntityKeyCache : IEntityKeyCache
    {
        /// <summary>
        /// This is the dictionary which is used for caching the key lookups.
        /// </summary>
        protected static Dictionary<string, PropertyInfo> KeyCache { get; set; }
        protected static Dictionary<string, MemberExpression> ExpressionCache { get; set; }

        static EntityKeyCache()
        {
            KeyCache = new Dictionary<string, PropertyInfo>();
            ExpressionCache = new Dictionary<string, MemberExpression>();
        }

        public object GetKeyValue(object entity)
        {
            return GetKeyProperty(entity.GetType()).GetValue(entity, null);
        }

        public string GetKeyName(Type entity)
        {
            PropertyInfo propInfo = GetKeyProperty(entity);

            if (propInfo == null)
                throw new Exception(string.Format("No key property found for entity {0}", entity.FullName));

            return propInfo.Name;
        }

        public Expression<Func<TEntity, bool>> GetExpression<TEntity>(object id)
        {
            var expr = GetExpression<TEntity>();

            var pe = (ParameterExpression)expr.Expression;
            var compareTo = Expression.Constant(id);
            var expression = Expression.Equal(expr, compareTo);
            return Expression.Lambda<Func<TEntity, bool>>(expression, new[] { pe });
        }

        /// <summary>
        /// Get a memberexpression from an object
        /// </summary>
        /// <returns></returns>
        private MemberExpression GetExpression<TEntity>()
        {
            MemberExpression expr = null;
            Type type = typeof(TEntity);

            if (!ExpressionCache.TryGetValue(type.FullName, out expr))
            {
                // Generate an expression and add it to the cache
                var propertyName = GetKeyName(type);
                var parameter = Expression.Parameter(type, "x");
                expr = Expression.Property(parameter, propertyName);
                ExpressionCache.Add(type.FullName, expr);
            }

            return expr;
        }

        /// <summary>
        /// Search the cache for the key attribute of the type.
        /// </summary>
        /// <param name="objectType">The type to use in the cache search.</param>
        /// <returns>Reference to a PropertyInfo object or null if none is found.</returns>
        private PropertyInfo GetKeyProperty(Type objectType)
        {
            PropertyInfo propInfo = null;
            // Search the cache for the key attribute of the given type
            if (!KeyCache.TryGetValue(objectType.FullName, out propInfo))
            {
                PropertyInfo[] properties = objectType.GetProperties();
                foreach (PropertyInfo pI in properties)
                {
                    System.Object[] attributes = pI.GetCustomAttributes(typeof(KeyAttribute), true);
                    if (attributes.Count() > 0)
                    {
                        Debug.WriteLine("Key property was not yet in the cache, added (" + pI.Name);
                        KeyCache.Add(objectType.FullName, pI);
                        propInfo = pI;
                        //return pI.GetValue(entity, null);
                    }
                }
            }

            return propInfo;
        }
    }
}
